﻿using System;
using System.Linq;
using System.Collections.Generic;
using HtmlAgilityPack;
using System.IO;

namespace LingDong.HtmlParser
{
    /// <summary>
    /// Represents a document that needs linked files to be rendered, 
    /// Such as images or css files, and points to other HTML documents.
    /// The code is base on Html Agility Pack's sample
    /// http://htmlagilitypack.codeplex.com/
    /// </summary>
    public class DocumentWithLinks
    {
        #region Constructors

        /// <summary>
        /// Creates an instance of a DocumentWithLinkedFiles.
        /// </summary>
        /// <param name="htmlContent">content of the html</param>
        public DocumentWithLinks(string htmlContent)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(htmlContent);
            Init(doc);
        }

        /// <summary>
        /// Creates an instance of a DocumentWithLinkedFiles.
        /// </summary>
        /// <param name="htmlContent">content of the html</param>
        /// <param name="url">URL of the html</param>
        public DocumentWithLinks(string htmlContent, string url)
        {
            this.url = url;
            fullDir = HtmlUtility.GetFullDir(url);
            fullDirCount = fullDir.Count;

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(htmlContent);
            Init(doc);
        }

        /// <summary>
        /// Creates an instance of a DocumentWithLinkedFiles.
        /// </summary>
        /// <param name="doc">The input HTML document. May not be null.</param>
        public DocumentWithLinks(HtmlDocument doc)
        {
            if (doc == null)
            {
                throw new ArgumentNullException("doc");
            }

            Init(doc);
        }

        private void Init(HtmlDocument doc)
        {
            this.Resources = new List<string>();
            this.References = new List<string>();
            this.ReferenceInnerText = new Dictionary<string,string>();
            this.doc = doc;

            GetResources();
            GetReferences();
        }

        #endregion


        #region Private Methods

        private void GetResources()
        {
            HtmlNodeCollection atts = doc.DocumentNode.SelectNodes("//*[@background or @lowsrc or @src or @href]");
            if (atts == null)
                return;

            foreach (HtmlNode n in atts)
            {
                ParseResource(n, "background");
                ParseResource(n, "href");
                ParseResource(n, "src");
                ParseResource(n, "lowsrc");
            }
        }


        private void ParseResource(HtmlNode node, string name)
        {
            HtmlAttribute att = node.Attributes[name];
            if (att == null)
                return;

            // if name = href, we are only interested by <link> tags
            if ((name == "href") && (node.Name != "link"))
                return;

            Resources.Add(att.Value);
        }

        private void GetReferences()
        {
            AddReferenceHref(doc.DocumentNode.SelectNodes("//a[@href]"));
            AddReferenceHref(doc.DocumentNode.SelectNodes("//area[@href]"));
            AddReferenceHref(doc.DocumentNode.SelectNodes("//link[@href]"));

            HtmlNodeCollection links = doc.DocumentNode.SelectNodes("//link");
            if (links != null)
            {
                foreach (HtmlNode link in links)
                {
                    AddReference(link.InnerText);
                }
            }

            References.RemoveAll(link => link == url);
        }

        private void AddReferenceHref(HtmlNodeCollection hrefs)
        {
            if (hrefs == null)
                return;

            foreach (HtmlNode href in hrefs)
            {
                AddReference(href.Attributes["href"].Value, href.InnerText);
            }
        }

        private void AddReference(string path)
        {
            AddReference(path, String.Empty);
        }

        private void AddReference(string path, string text)
        {
            if (!HtmlUtility.IsHtmlLink(path))
                return;

            string fullPath = HtmlUtility.GetFullPath(this.url, path, fullDir, fullDirCount);
            if (String.IsNullOrEmpty(fullPath))
                return;

            string formattedPath = HtmlUtility.GetFormattedDomain(fullPath);
            References.Add(formattedPath);
            if (!String.IsNullOrEmpty(text))
            {
                ReferenceInnerText[formattedPath] = text;
            }
        }

        #endregion


        #region Feilds

        /// <summary>
        /// Gets a list of resource links as they are declared in the HTML document.
        /// </summary>
        public List<string> Resources { get; private set; }

        /// <summary>
        /// Gets a list of reference links to other HTML documents, as they are declared in the HTML document.
        /// </summary>
        public List<string> References { get; private set; }

        /// <summary>
        /// Inner text of links, <a>innertext</a>
        /// </summary>
        public Dictionary<string, string> ReferenceInnerText { get; private set; }

        #endregion


        #region Private Members

        private HtmlDocument doc;
        private string url;
        private List<string> fullDir;
        private int fullDirCount;

        #endregion
    }
}
